#include <Connectivity/TCPClient.hpp>

TCPClient::TCPClient(std::string &senderAddress) {

	this->senderAddress = senderAddress;

}

TCPConnection *TCPClient::openConnection(std::string &port, std::string &address) {

	int status;
	int sock;
	struct sockaddr_in6 *destinationSockAddress = (struct sockaddr_in6*) malloc(sizeof(struct sockaddr_in6));
	struct sockaddr_in6 senderSockAddress;
	struct addrinfo hints, *res, *p;

	/*
	 * Create IPv6 socket
	 */
	sock = socket( AF_INET6, SOCK_STREAM, 0);
	if (sock < 0) {
		std::cerr << "Error: Socket" << std::endl;
		return 0;
	}

	int yes = 1;
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));

	/*
	 * Bind sender address
	 */
	memset(&senderSockAddress, 0, sizeof(sockaddr_in6));

	in6_addr senderInterfaceAddress;
	inet_pton(AF_INET6, senderAddress.c_str(), &senderInterfaceAddress);

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_INET6;
	hints.ai_socktype = SOCK_STREAM;

	status = getaddrinfo(senderAddress.c_str(), NULL, &hints, &res);
	if (status) {
		std::cerr << "Error: getaddrinfo " << gai_strerror(status);
		return 0;
	}

	for (p = res; p != NULL; p = p->ai_next) {

		struct sockaddr_in6 *currentAddress;

		currentAddress = (struct sockaddr_in6 *) p->ai_addr;

		if (memcmp(&(currentAddress->sin6_addr), &senderInterfaceAddress, sizeof(in6_addr)) == 0) {
			senderSockAddress = *currentAddress;
		}

	}

	senderSockAddress.sin6_scope_id = 4; // Nasty
	freeaddrinfo(res);

	/* Bind and check */
	status = bind(sock, (struct sockaddr*) &senderSockAddress, sizeof(struct sockaddr_in6));
	if (status < 0) {
		std::cerr << "Error: Binding sender address " << (int) errno << std::endl;
		return 0;
	}

	/*
	 * Setup destination address
	 */
	memset(destinationSockAddress, 0, sizeof(sockaddr_in6));

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_INET6;
	hints.ai_socktype = SOCK_STREAM;

	in6_addr targetInterfaceAddress;
	inet_pton(AF_INET6, address.c_str(), &targetInterfaceAddress);

	status = getaddrinfo(address.c_str(), port.c_str(), &hints, &res);
	if (status) {
		std::cerr << "Error: getaddrinfo " << gai_strerror(status);
		return 0;
	}

	for (p = res; p != NULL; p = p->ai_next) {

		struct sockaddr_in6 *currentAddress;

		currentAddress = (struct sockaddr_in6 *) p->ai_addr;

		if (memcmp(&(currentAddress->sin6_addr), &targetInterfaceAddress, sizeof(in6_addr)) == 0) {
			*destinationSockAddress = *currentAddress;
		}

	}

	destinationSockAddress->sin6_scope_id = 4; // Nasty
	freeaddrinfo(res);

	/*
	 * Connect to server and return a new TCP connection
	 */
	if (connect(sock, (struct sockaddr*) destinationSockAddress, sizeof(struct sockaddr_in6)) < 0) {
		std::cerr << "Error: Connect" << std::endl;
		return 0;
	} else {
		return new TCPConnection(sock, (struct sockaddr*) destinationSockAddress);
	}
}

